﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;

namespace MyUtils
{

	public class CommandLineParser
	{
		public interface IExecutor
		{
			void Execute(string commandOrOption, string parameter);
			void Finish();
		}

		public class ReflectionExecutor : IExecutor
		{
			public object Target;

			public ReflectionExecutor()
			{
				
			}

			public ReflectionExecutor(object target)
			{
				Target = target;
			}

			void RunQueued()
			{
				while(queue.Count>0)
				{
					var item = queue.Dequeue();
					Execute(item[0], item[1], true);
				}
			}

			readonly Queue<string[]> queue = new Queue<string[]>();

			public void Execute(string commandOrOption, string parameter)
			{
				Execute(commandOrOption, parameter, false);
			}

			public void Execute(string commandOrOption, string parameter, bool now)
			{
				if(Target == null)
				{
					throw new InvalidOperationException("Target is not defined");
				}
				var members = Target.GetType().GetMember(commandOrOption, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);

				if(members.Length>1)
				{
					throw new ArgumentException("Ambigious parameter: " + commandOrOption + ". There are one of: " + string.Join(", ", members.Select(x => x.ReflectedType.Name + "." + x.Name + "(" + x.MemberType + ")").ToArray()));
				}

				var member = members.FirstOrDefault();

				var property = member as PropertyInfo;
				if (property != null)
				{
					if (property.GetIndexParameters().Length > 0)
					{
						throw new ArgumentException("Can not use Indexer as a parameter");
					}
					property.SetValue(Target, ConvertTo(property.PropertyType, parameter), null);
				}
				else
				{
					var field = member as FieldInfo;
					if (field != null)
					{
						field.SetValue(Target, ConvertTo(field.FieldType, parameter));
					}
					else
					{
						var method = member as MethodInfo;
						if (method != null)
						{
							if (!now)
							{
								queue.Enqueue(new[] {commandOrOption, parameter});
							}
							else
							{
								var args = new object[method.GetParameters().Length];
								if (args.Length > 1)
								{
									var parameters = parameter.Split(' ').Where(x=>!string.IsNullOrEmpty(x)).ToArray();
									if (parameters.Length != args.Length)
									{
										throw new ArgumentException("Command " + commandOrOption + " requires " + args.Length + " parameters, but you provided " + parameters.Length);
									}
									for (int j = 0; j < args.Length; j++)
									{
										args[j] = ConvertTo(method.GetParameters()[j].ParameterType, parameters[j]);
									}
								}
								else if (args.Length == 1)
								{
									args[0] = ConvertTo(method.GetParameters()[0].ParameterType, parameter);
								}
								method.Invoke(Target, args);
							}
						}
						else
						{
							switch(commandOrOption.ToUpperInvariant())
							{
								case "HELP":
								case "?":
									throw new ArgumentException(GetHelpText());
							}
							throw new ArgumentException("Unknown command: " + commandOrOption);
						}
					}
				}
			}

			string GetHelpText()
			{
				var msg = new StringBuilder();
				msg.AppendLine("Usage:");
				foreach (var member in Target.GetType().GetMembers())
				{
					if(member.DeclaringType==typeof(object))
					{
						continue;
					}
					var attrib = (DefaultValueAttribute)(member.GetCustomAttributes(typeof (DefaultValueAttribute), true).FirstOrDefault() ?? new DefaultValueAttribute("No help provided"));
					msg.AppendFormat("  {0}: {1}", member, attrib.Value);
				}
				return msg.ToString();
			}

			public void Finish()
			{
				RunQueued();
			}
		}

		static object ConvertTo(Type type, string value)
		{
			if (type == typeof (string))
			{
				return value;
			}
			if (typeof (IConvertible).IsAssignableFrom(type))
			{
				return (value as IConvertible).ToType(type, CultureInfo.CurrentCulture);
			}
			if (type.GetGenericTypeDefinition() == typeof (Nullable<>))
			{
				var realtype = Nullable.GetUnderlyingType(type);
				return ConvertTo(realtype, value);
			}
			throw new ArgumentException("Can not convert string '" + value + "' to " + type);
		}

		public IExecutor Executor;

		public CommandLineParser()
		{
			
		}

		public CommandLineParser(IExecutor executor)
		{
			Executor = executor;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="target">Target to use with reflection executor</param>
		public CommandLineParser(object target)
		{
			Executor = new ReflectionExecutor(target);
		}

/*
		public void Parse(string argsAsSingleLine)
		{
			
		}
*/

		public void Parse(params string[] args)
		{
			args = args.Where(x => !string.IsNullOrEmpty(x)).ToArray();
			if (Executor == null)
			{
				throw new InvalidOperationException("Executor is not specified");
			}
			foreach (var s_ in args)
			{
				var s = s_.Trim();
				if (s.StartsWith("-") || s.StartsWith("/"))
				{
					s = s.Substring(1);
				}
				int i = s.IndexOf(' ');
				if(i<0)
				{
					i = s.IndexOf('=');
				}
				string cmd, param = string.Empty;
				if (i > 0)
				{
					cmd = s.Substring(0, i);
					param = s.Substring(i + 1);
				}
				else
				{
					cmd = s;
				}
				Executor.Execute(cmd, param);
			}
			Executor.Finish();
		}

#if !PocketPC
		/// <summary>
		/// Parse ars that passed to current process
		/// </summary>
		public void Parse()
		{
			Parse(Environment.GetCommandLineArgs());
		}
#endif

	}
}
